/*
*  This file is part of mangusta
*
*  mangusta is free software; you can redistribute it and/or modify
*  it under the terms of the GNU General Public License as published by
*  the Free Software Foundation; either version 2 of the License, or
*  (at your option) any later version.
*
*  mangusta is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU General Public License for more details.
*
*  You should have received a copy of the GNU General Public License
*  along with mangusta; if not, write to the Free Software
*  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  US
*
*  Author: <antonino.calderone@ericsson.com>, <acaldmail@gmail.com>
*
*/


/* -------------------------------------------------------------------------- */

#ifndef __NU_PARSER_H__
#define __NU_PARSER_H__

#include "nu_var_scope.h"

#include "nu_functions.h"
#include "nu_expr_parser.h"
#include "nu_expr_literal.h"
#include "nu_expr_tknzr.h"
#include "nu_stmt.h"
#include "nu_stmt_empty.h"
#include "nu_stmt_on_goto.h"

#include <set>


/* -------------------------------------------------------------------------- */

namespace nu
{
class parser_t
{
protected:
   prog_pointer_t::line_number_t _parsing_line = 0;
   prog_pointer_t::stmt_number_t _parsing_stmt = 0;

   // ---------------------------------------------------------------------

public:

   static void remove_blank(nu::token_list_t & tl);

   // ---------------------------------------------------------------------

   static void extract_next_token(
      token_list_t& tl,
      token_t & token,
      std::function<bool(const token_list_t& tl, const token_t& token)> check =
         [](const token_list_t& tl, const token_t& token)
   {
      return tl.empty();
   });


   // ---------------------------------------------------------------------

   static void move_sub_expression(
      token_list_t& source_tl,
      token_list_t& dst_tl,
      const std::string& id,
      tkncl_t idtype);

protected:


   //! parse single stamtement
   stmt_t::handle_t parse_stmt(
      prog_ctx_t & ctx,
      nu::token_list_t & tl);

   //! parse a block of statement
   stmt_t::handle_t parse_block(
      prog_ctx_t & ctx,
      nu::token_list_t & tl,
      const std::string& end_block_id = "",
      tkncl_t end_block_id_cl = tkncl_t::UNDEFINED
   );

   //! parse goto statement
   template<class T>
   stmt_t::handle_t parse_branch_instr(
      prog_ctx_t & ctx,
      token_t token,
      token_list_t & tl);

   //! parse for statement
   stmt_t::handle_t parse_for(
      prog_ctx_t & ctx,
      token_t token,
      token_list_t & tl);

   //! parse class statement
   stmt_t::handle_t parse_class(
      prog_ctx_t & ctx,
      token_t token,
      token_list_t & tl);

   //! parse break statement
   stmt_t::handle_t parse_break(
      prog_ctx_t & ctx,
      token_t token,
      token_list_t & tl);

   //! parse function statement
   template<class T>
   stmt_t::handle_t parse_func(
      prog_ctx_t & ctx,
      token_t token,
      token_list_t & tl);

   //! parse a label list
   stmt_on_goto_t::label_list_t parse_label_list(
      prog_ctx_t & ctx,
      nu::token_t token,
      nu::token_list_t & tl);

   //! parse instruction parameter list
   template<class T, typename ...E>
   stmt_t::handle_t parse_parameter_list(
      nu::tkncl_t begin_list_token,
      nu::tkncl_t end_list_token,
      prog_ctx_t & ctx,
      nu::token_t token,
      nu::token_list_t & tl,
      const std::string & end_token,
      E&&... xprms)
   {
      --tl;
      remove_blank(tl);

      auto scan_token = [&](tkncl_t tc)
      {
         extract_next_token(tl, token);
         syntax_error_if(token.type() != tc, token.expression(), token.position());
      };

      stmt_t::handle_t handle(std::make_shared<T>(std::forward<E>(xprms)...));

      while (!tl.empty() &&
             (tl.begin()->type() != tkncl_t::OPERATOR &&
              tl.begin()->identifier() != end_token))
      {
         bool reference = false;
         bool array_type = false;

         token = *tl.begin();

         syntax_error_if(token.type() != tkncl_t::IDENTIFIER,
                         token.expression(), token.position());

         if (token.type() == tkncl_t::IDENTIFIER &&
               token.identifier() == PROGLANG_STMT_ARRAY)
         {
            --tl;
            remove_blank(tl);
            token = *tl.begin();
            array_type = true;
         }

         std::string variable_type = token.identifier();

         --tl;
         remove_blank(tl);
         token = *tl.begin();

         if (token.type() == tkncl_t::OPERATOR &&
               token.identifier() == PROGLANG_REFERENCE_OP)
         {
            --tl;
            remove_blank(tl);
            token = *tl.begin();
            reference = true;
         }

         syntax_error_if(token.type() != tkncl_t::IDENTIFIER,
                         token.expression(), token.position());

         std::string variable_name = token.identifier();

         extract_next_token(tl, token);

         if (token.type() == begin_list_token)
         {
            scan_token(tkncl_t::INTEGRAL);

            token_list_t etl;
            expr_parser_t ep;

            etl += token;

            T * ptr = dynamic_cast<T*>(handle.get());
            assert(ptr);

            ptr->define(
               variable_name,
               variable_type,
               reference,
               array_type,
               ep.compile(etl, token.position()),
               std::forward<E>(xprms)...);

            scan_token(end_list_token);

            --tl;
            remove_blank(tl);
         }

         else
         {
            T * ptr = dynamic_cast<T*>(handle.get());
            assert(ptr);

            ptr->define(
               variable_name,
               variable_type,
               reference,
               array_type,
               std::make_shared<expr_literal_t>(0),
               std::forward<E>(xprms)...);
         }

         if (tl.empty())
            break;

         token = *tl.begin();

         syntax_error_if(
            (token.type() != tkncl_t::OPERATOR &&
             token.type() != end_list_token)
            ||
            (token.identifier() != PROGLANG_PARAM_SEP &&
             token.identifier() != end_token),
            token.expression(), token.position());

         if (token.identifier() == end_token)
         {
            if (end_token == PROGLANG_END_SUBEXPR_OP ||
                  end_token == PROGLANG_END_SUBSCR_OP)
            {
               --tl;
               remove_blank(tl);
            }

            //A procedure parameter list must be terminated by ")"
            //No other tokens in the line can be present
            syntax_error_if(
               (end_token == PROGLANG_END_SUBEXPR_OP ||
                end_token == PROGLANG_END_SUBSCR_OP) && !tl.empty(),
               token.expression(),
               token.position());

            break;
         }

         --tl;
         remove_blank(tl);
      }

      return handle;
   }


   //! parse if-statement
   template<class T = stmt_if_t>
   stmt_t::handle_t parse_if_stmt(
      prog_ctx_t & ctx,
      token_t token,
      token_list_t & tl)
   {
      --tl;
      remove_blank(tl);
      syntax_error_if(tl.empty(), token.expression(), token.position());

      token = *tl.begin();
      --tl;
      remove_blank(tl);

      syntax_error_if(
         token.identifier() != PROGLANG_BEGIN_SUBEXPR_OP ||
         token.type() != tkncl_t::SUBEXP_BEGIN ||
         tl.empty(), token.expression(), token.position());

      token = *tl.rbegin();
      tl--;
      remove_blank(tl);

      syntax_error_if(
         token.identifier() != PROGLANG_BEGIN_BLOCK_OP ||
         token.type() != tkncl_t::IDENTIFIER ||
         tl.empty(), token.expression(), token.position());


      token = *tl.rbegin();
      tl--;
      remove_blank(tl);

      syntax_error_if(
         token.identifier() != PROGLANG_END_SUBEXPR_OP ||
         token.type() != tkncl_t::SUBEXP_END ||
         tl.empty(), token.expression(), token.position());

      expr_parser_t ep;
      expr_any_t::handle_t condition = ep.compile(tl, token.position() + 1);
      tl.clear();

      return stmt_t::handle_t(std::make_shared<T>(ctx, condition));
   }

   //! parse while-statement
   stmt_t::handle_t parse_while(
      prog_ctx_t & ctx,
      token_t token,
      token_list_t & tl);

   //! parse loop-while-statement ...
   stmt_t::handle_t parse_loop_while(
      prog_ctx_t & ctx,
      token_t token,
      token_list_t & tl);

   //! parse #fd, get, ...
   void parse_fd_args(
      prog_ctx_t & ctx,
      token_t token,
      token_list_t & tl,
      int & fd,
      var_list_t & var_list);

   //! parse var
   var_arg_t parse_var_arg(
      prog_ctx_t & ctx,
      token_t token,
      token_list_t & tl);

   //! parse declaration
   stmt_t::handle_t parse_declaration(
      prog_ctx_t & ctx,
      token_list_t & tl,
      bool constant,
      variable_t::type_t type);

   //! parse cout statement
   stmt_t::handle_t parse_cout(
      prog_ctx_t & ctx,
      token_t token,
      token_list_t & tl);

   //! parse cout statement
   stmt_t::handle_t parse_expr(
      prog_ctx_t & ctx,
      token_t token,
      token_list_t & tl);

   //! parse LABEL identifier
   stmt_t::handle_t parse_label(
      prog_ctx_t & ctx,
      token_t token,
      token_list_t & tl);

   //! parse 'goto' statement
   stmt_t::handle_t parse_goto_stmt(
      prog_ctx_t & ctx,
      token_t token,
      nu::token_list_t & tl);

   //! parse 'end' statement
   stmt_t::handle_t parse_end(
      prog_ctx_t & ctx,
      token_t token,
      nu::token_list_t & tl);

   //! parse '} [if|while|func]' statement
   stmt_t::handle_t parse_end_stmt(
      prog_ctx_t & ctx,
      token_t token,
      nu::token_list_t & tl);

   //! parse do statement
   stmt_t::handle_t parse_do_stmt(
      prog_ctx_t & ctx,
      token_t token,
      nu::token_list_t & tl);

   //! parse instruction and its args
   template <class T, typename ...E>
   stmt_t::handle_t parse_generic_instruction(
      prog_ctx_t & ctx,
      token_t token,
      nu::token_list_t & tl,
      E&& ... xargs)
   {
      --tl;
      remove_blank(tl);

      syntax_error_if(tl.empty(), token.expression(), token.position());

      return parse_arg_list<T, 0>(
                ctx,
                token,
                tl,
                [](const token_t&t)
      {
         return t.type() == tkncl_t::OPERATOR &&
                (t.identifier() == PROGLANG_PARAM_SEP);
      },
      std::forward<E>(xargs)...
             );
   }

   //! parse argument list
   //  T is instruction statement class to instantiate
   //  NARGS is number of argument to check, 0 means "no-check"
   //  ...E is extra-argument type list (passed to T-class-ctor)
   template <class T, int NARGS, typename ...E>
   stmt_t::handle_t parse_arg_list(
      prog_ctx_t & ctx,
      token_t token,
      token_list_t & tl,
      std::function<bool(const token_t&)> is_separator,
      E&&... xargs)
   {
      expr_parser_t ep;
      arg_list_t args;
      auto pos = token.position();
      int parenthesis_level = 0;
      int subscr_par_level = 0;

      int nargs = NARGS;
      bool check_args = nargs > 0;

      while (!tl.empty() && (!check_args || nargs > 0))
      {
         token_list_t etl;

         remove_blank(tl);

         while (!tl.empty())
         {
            const token_t token(*tl.begin());

            int tkt = static_cast<int>(token.type());

            switch ( tkt )
            {
               case tkncl_t::SUBEXP_BEGIN:
                  ++parenthesis_level;
                  break;

               case tkncl_t::SUBEXP_END:
                  --parenthesis_level;
                  break;

               case tkncl_t::SUBSCR_BEGIN:
                  ++parenthesis_level;
                  ++subscr_par_level;
                  break;

               case tkncl_t::SUBSCR_END:
                  --parenthesis_level;
                  --subscr_par_level;
                  break;
            }

            if (token.type() == tkncl_t::OPERATOR)
            {
               if (token.identifier() == PROGLANG_STMNT_SEP)
               {
                  if (!etl.empty())
                  {
                     args.push_back(
                        std::make_pair(ep.compile(etl, pos), '\0'));
                  }

                  return stmt_t::handle_t(new T(args, std::forward<E>(xargs)...));
               }

               if (parenthesis_level < 1 && is_separator(token))
               {
                  expr_any_t::handle_t eh(ep.compile(etl, pos));

                  args.push_back(
                     std::make_pair(
                        eh, token.identifier().c_str()[0]));

                  --nargs;

                  --tl;

                  remove_blank(tl);

                  if (tl.empty())
                  {
                     return stmt_t::handle_t(
                               std::make_shared<T>(
                                  args, std::forward<E>(xargs)...));
                  }

                  break;
               }
            }

            etl += token;
            --tl;

            remove_blank(tl);
         }

         remove_blank(tl);

         if (tl.empty())
         {
            if (!etl.empty())
            {
               args.push_back(
                  std::make_pair(ep.compile(etl, pos), '\0'));
            }

            return stmt_t::handle_t(
                      std::make_shared<T>(args, std::forward<E>(xargs)...));
         }
      }

      return stmt_t::handle_t(std::make_shared<T>(std::forward<E>(xargs)...));
   }


   //! parse a sub-expression
   expr_any_t::handle_t parse_sub_expr(
      prog_ctx_t & ctx,
      token_t token,
      token_list_t & tl,
      token_list_t & vect_etl,
      bool subscription_operators);


public:
   parser_t() = default;
   parser_t(const parser_t&) = default;
   parser_t& operator=(const parser_t&) = default;

   stmt_t::handle_t compile_line(nu::expr_tknzr_t & st, prog_ctx_t & ctx);
   stmt_t::handle_t compile_line(nu::token_list_t & tl, prog_ctx_t & ctx);

};


/* -------------------------------------------------------------------------- */

}


/* -------------------------------------------------------------------------- */

#endif // __NU_PARSER_H__
